feat: add shadow-styles export for Shadow DOM integration#1027
feat: add shadow-styles export for Shadow DOM integration#1027makhnatkin wants to merge 4 commits intomainfrom
Conversation
8ca0e13 to
cee414e
Compare
Reviewer's GuideAdds a new Shadow DOM–friendly stylesheet export ( File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
EXTERNAL_CSS_IMPORT_REapproach hard-codes a particular import shape (single-line, static, bare module,.csssuffix) and may miss or mis-detect imports as code evolves; consider either broadening the regex to handle multiline/named-only imports or switching to a simple AST-based scan so TS/JS formatting changes don’t silently break CSS collection. - In
collectExternalCss,require.resolve(cssImport)will throw if a referenced CSS module is missing; if this is expected to be robust in different consumer setups, you might want to add a clearer error message or guard around unresolved imports so build failures are easier to diagnose.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `EXTERNAL_CSS_IMPORT_RE` approach hard-codes a particular import shape (single-line, static, bare module, `.css` suffix) and may miss or mis-detect imports as code evolves; consider either broadening the regex to handle multiline/named-only imports or switching to a simple AST-based scan so TS/JS formatting changes don’t silently break CSS collection.
- In `collectExternalCss`, `require.resolve(cssImport)` will throw if a referenced CSS module is missing; if this is expected to be robust in different consumer setups, you might want to add a clearer error message or guard around unresolved imports so build failures are easier to diagnose.
## Individual Comments
### Comment 1
<location path="packages/editor/gulpfile.mjs" line_range="26" />
<code_context>
nodeModulesDir: NODE_MODULES_DIR,
});
+task('styles-string', (done) => {
+ const externalCss = collectExternalCss();
+ const editorCss = readFileSync(resolve(BUILD_DIR, 'styles.css'), 'utf8');
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the CSS aggregation and helper logic into a separate module so the gulpfile remains a small, declarative task wiring file.
You can keep the new functionality but move the “mini build system” out of the gulpfile so the gulpfile stays small and easy to scan.
### 1. Extract helpers into a separate module
Create a dedicated helper file (e.g. `build/styles-string.mjs`) and move the regex, FS traversal, and template-escaping there:
```js
// build/styles-string.mjs
import {readFileSync, readdirSync} from 'node:fs';
import {createRequire} from 'node:module';
import {extname, resolve} from 'node:path';
const require = createRequire(import.meta.url);
const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx']);
const EXTERNAL_CSS_IMPORT_RE =
/^\s*import\s+(?:.+?\s+from\s+)?['"]([^./'"][^'"]*\.css)['"];?/gm;
export function createStylesString({buildDir, sourceDir}) {
const externalCss = collectExternalCss(sourceDir);
const editorCss = readFileSync(resolve(buildDir, 'styles.css'), 'utf8');
const styles = [externalCss, editorCss].filter(Boolean).join('\n');
return toTemplateLiteral(styles);
}
function collectExternalCss(sourceDir) {
const cssImports = new Set();
for (const sourceFile of getSourceFiles(sourceDir)) {
const sourceCode = readFileSync(sourceFile, 'utf8');
for (const match of sourceCode.matchAll(EXTERNAL_CSS_IMPORT_RE)) {
cssImports.add(match[1]);
}
}
return Array.from(cssImports)
.map((cssImport) => readFileSync(require.resolve(cssImport), 'utf8'))
.join('\n');
}
function getSourceFiles(dir) {
return readdirSync(dir, {withFileTypes: true})
// drop sort() if deterministic order is not required
.flatMap((entry) => {
const entryPath = resolve(dir, entry.name);
if (entry.isDirectory()) {
return getSourceFiles(entryPath);
}
return SOURCE_EXTENSIONS.has(extname(entry.name)) ? [entryPath] : [];
});
}
function toTemplateLiteral(value) {
return `\`${value
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$\{/g, '\\${')}\``;
}
```
### 2. Keep the gulpfile focused on task wiring
Then the gulpfile only wires tasks together and delegates the logic:
```js
// gulpfile.mjs
import {writeFileSync} from 'node:fs';
import {dirname, resolve} from 'node:path';
import {fileURLToPath} from 'node:url';
import {parallel, series, task} from '@markdown-editor/gulp-tasks';
import {registerBuildTasks} from '@markdown-editor/gulp-tasks/build';
import {createStylesString} from './build/styles-string.mjs';
import pkg from './package.json' with {type: 'json'};
const __dirname = dirname(fileURLToPath(import.meta.url));
const BUILD_DIR = resolve('build');
const SOURCE_DIR = resolve('src');
registerBuildTasks({
version: pkg.version,
buildDir: BUILD_DIR,
nodeModulesDir: resolve(__dirname, 'node_modules'),
});
task('styles-string', (done) => {
const content = createStylesString({buildDir: BUILD_DIR, sourceDir: SOURCE_DIR});
writeFileSync(resolve(BUILD_DIR, 'styles-string.mjs'), `export default ${content};\n`);
writeFileSync(resolve(BUILD_DIR, 'styles-string.cjs'), `module.exports = ${content};\n`);
writeFileSync(
resolve(BUILD_DIR, 'styles-string.d.ts'),
'declare const styles: string;\nexport default styles;\n',
);
done();
});
task('build', series(parallel('ts', 'json', 'scss'), 'styles-string'));
task('default', series('clean', 'build'));
```
This keeps all behavior intact but:
- The gulpfile returns to being a small, declarative task definition.
- The complex parts (regex parsing, traversal, escaping) live in an isolated module that you can unit test independently.
- Future readers only need to dive into `build/styles-string.mjs` when they care about the details of CSS aggregation.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| nodeModulesDir: NODE_MODULES_DIR, | ||
| }); | ||
|
|
||
| task('styles-string', (done) => { |
There was a problem hiding this comment.
issue (complexity): Consider extracting the CSS aggregation and helper logic into a separate module so the gulpfile remains a small, declarative task wiring file.
You can keep the new functionality but move the “mini build system” out of the gulpfile so the gulpfile stays small and easy to scan.
1. Extract helpers into a separate module
Create a dedicated helper file (e.g. build/styles-string.mjs) and move the regex, FS traversal, and template-escaping there:
// build/styles-string.mjs
import {readFileSync, readdirSync} from 'node:fs';
import {createRequire} from 'node:module';
import {extname, resolve} from 'node:path';
const require = createRequire(import.meta.url);
const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx']);
const EXTERNAL_CSS_IMPORT_RE =
/^\s*import\s+(?:.+?\s+from\s+)?['"]([^./'"][^'"]*\.css)['"];?/gm;
export function createStylesString({buildDir, sourceDir}) {
const externalCss = collectExternalCss(sourceDir);
const editorCss = readFileSync(resolve(buildDir, 'styles.css'), 'utf8');
const styles = [externalCss, editorCss].filter(Boolean).join('\n');
return toTemplateLiteral(styles);
}
function collectExternalCss(sourceDir) {
const cssImports = new Set();
for (const sourceFile of getSourceFiles(sourceDir)) {
const sourceCode = readFileSync(sourceFile, 'utf8');
for (const match of sourceCode.matchAll(EXTERNAL_CSS_IMPORT_RE)) {
cssImports.add(match[1]);
}
}
return Array.from(cssImports)
.map((cssImport) => readFileSync(require.resolve(cssImport), 'utf8'))
.join('\n');
}
function getSourceFiles(dir) {
return readdirSync(dir, {withFileTypes: true})
// drop sort() if deterministic order is not required
.flatMap((entry) => {
const entryPath = resolve(dir, entry.name);
if (entry.isDirectory()) {
return getSourceFiles(entryPath);
}
return SOURCE_EXTENSIONS.has(extname(entry.name)) ? [entryPath] : [];
});
}
function toTemplateLiteral(value) {
return `\`${value
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$\{/g, '\\${')}\``;
}2. Keep the gulpfile focused on task wiring
Then the gulpfile only wires tasks together and delegates the logic:
// gulpfile.mjs
import {writeFileSync} from 'node:fs';
import {dirname, resolve} from 'node:path';
import {fileURLToPath} from 'node:url';
import {parallel, series, task} from '@markdown-editor/gulp-tasks';
import {registerBuildTasks} from '@markdown-editor/gulp-tasks/build';
import {createStylesString} from './build/styles-string.mjs';
import pkg from './package.json' with {type: 'json'};
const __dirname = dirname(fileURLToPath(import.meta.url));
const BUILD_DIR = resolve('build');
const SOURCE_DIR = resolve('src');
registerBuildTasks({
version: pkg.version,
buildDir: BUILD_DIR,
nodeModulesDir: resolve(__dirname, 'node_modules'),
});
task('styles-string', (done) => {
const content = createStylesString({buildDir: BUILD_DIR, sourceDir: SOURCE_DIR});
writeFileSync(resolve(BUILD_DIR, 'styles-string.mjs'), `export default ${content};\n`);
writeFileSync(resolve(BUILD_DIR, 'styles-string.cjs'), `module.exports = ${content};\n`);
writeFileSync(
resolve(BUILD_DIR, 'styles-string.d.ts'),
'declare const styles: string;\nexport default styles;\n',
);
done();
});
task('build', series(parallel('ts', 'json', 'scss'), 'styles-string'));
task('default', series('clean', 'build'));This keeps all behavior intact but:
- The gulpfile returns to being a small, declarative task definition.
- The complex parts (regex parsing, traversal, escaping) live in an isolated module that you can unit test independently.
- Future readers only need to dive into
build/styles-string.mjswhen they care about the details of CSS aggregation.
Add a regression script that diffs SHADOW_STYLE_IMPORTS against non-relative *.css imports under packages/editor/src/**, so a future extension addition cannot silently leave shadow-styles cssText stale behind a green CI. Wired as a separate ci:test:shadow-styles job mirroring the circular-deps check. Wrap require.resolve() for shadow-styles externals in a try/catch: optional peer packages (per peerDependenciesMeta) are skipped with a warning instead of crashing the build for consumers that don't install them. Required peers still throw. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
CSS_IMPORT_REincheck-shadow-styles-imports.jsonly matches bare side-effect imports (e.g.import 'x.css'); if any file ever usesimport x from 'x.css'or other syntaxes, those CSS dependencies will be missed, so consider broadening the regex to cover allimport ... from '...css'forms. - The drift-check script dynamically imports
gulpfile.mjsjust to readSHADOW_STYLE_IMPORTS, which also runs all gulpfile side effects; consider movingSHADOW_STYLE_IMPORTSinto a small shared module that both the gulpfile and the script can import without triggering task registration.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `CSS_IMPORT_RE` in `check-shadow-styles-imports.js` only matches bare side-effect imports (e.g. `import 'x.css'`); if any file ever uses `import x from 'x.css'` or other syntaxes, those CSS dependencies will be missed, so consider broadening the regex to cover all `import ... from '...css'` forms.
- The drift-check script dynamically imports `gulpfile.mjs` just to read `SHADOW_STYLE_IMPORTS`, which also runs all gulpfile side effects; consider moving `SHADOW_STYLE_IMPORTS` into a small shared module that both the gulpfile and the script can import without triggering task registration.
## Individual Comments
### Comment 1
<location path="packages/editor/scripts/check-shadow-styles-imports.js" line_range="9-10" />
<code_context>
+const SRC_DIR = path.resolve(__dirname, '..', 'src');
+const GULPFILE_URL = pathToFileURL(path.resolve(__dirname, '..', 'gulpfile.mjs')).href;
+
+// Bare `import 'pkg/path/file.css';` — non-relative, scoped or unscoped, ending in .css.
+const CSS_IMPORT_RE = /(?:^|\s)import\s+['"]((?:@[^'"\s/]+\/)?[^'"\s.][^'"\s]*\.css)['"]/gm;
+
+const EXCLUDED_SCOPES = ['@gravity-ui/'];
</code_context>
<issue_to_address>
**suggestion:** The regex will also pick up commented-out imports, which can cause noisy false positives.
Since `CSS_IMPORT_RE` doesn’t account for comments, lines like `// import '@foo/bar.css';` or imports inside block comments will still be matched and counted, potentially failing the drift check on dead imports. To avoid this, either narrow the regex (e.g., anchor to `^\s*import` and ensure it’s not preceded by `//`) or pre-strip line and block comments before applying the regex.
Suggested implementation:
```javascript
// Bare `import 'pkg/path/file.css';` — non-relative, scoped or unscoped, ending in .css.
// Note: anchored to the start of the line to avoid matching inline occurrences.
const CSS_IMPORT_RE = /^\s*import\s+['"]((?:@[^'"\s/]+\/)?[^'"\s.][^'"\s]*\.css)['"]/gm;
const EXCLUDED_SCOPES = ['@gravity-ui/'];
// Remove line (`// ...`) and block (`/* ... */`) comments from source code.
const LINE_COMMENT_RE = /\/\/[^\n\r]*/g;
const BLOCK_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
function stripComments(source) {
if (typeof source !== 'string') {
return '';
}
// Remove block comments first, then line comments.
return source.replace(BLOCK_COMMENT_RE, '').replace(LINE_COMMENT_RE, '');
}
```
` since that part isn’t shown).
Here are the edits:
<file_operations>
<file_operation operation="edit" file_path="packages/editor/scripts/check-shadow-styles-imports.js">
<<<<<<< SEARCH
// Bare `import 'pkg/path/file.css';` — non-relative, scoped or unscoped, ending in .css.
const CSS_IMPORT_RE = /(?:^|\s)import\s+['"]((?:@[^'"\s/]+\/)?[^'"\s.][^'"\s]*\.css)['"]/gm;
const EXCLUDED_SCOPES = ['@gravity-ui/'];
=======
// Bare `import 'pkg/path/file.css';` — non-relative, scoped or unscoped, ending in .css.
// Note: anchored to the start of the line to avoid matching inline occurrences.
const CSS_IMPORT_RE = /^\s*import\s+['"]((?:@[^'"\s/]+\/)?[^'"\s.][^'"\s]*\.css)['"]/gm;
const EXCLUDED_SCOPES = ['@gravity-ui/'];
// Remove line (`// ...`) and block (`/* ... */`) comments from source code.
const LINE_COMMENT_RE = /\/\/[^\n\r]*/g;
const BLOCK_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
function stripComments(source) {
if (typeof source !== 'string') {
return '';
}
// Remove block comments first, then line comments.
return source.replace(BLOCK_COMMENT_RE, '').replace(LINE_COMMENT_RE, '');
}
>>>>>>> REPLACE
</file_operation>
</file_operations>
<additional_changes>
To fully wire this up you should:
1. In `collectCssImports` (or wherever file contents are scanned using `CSS_IMPORT_RE`), ensure you call `stripComments` on the file contents before applying the regex. For example, if you currently have something like:
```js
const content = fs.readFileSync(filePath, 'utf8');
let match;
while ((match = CSS_IMPORT_RE.exec(content)) !== null) {
// ...
}
```
change it to:
```js
const content = fs.readFileSync(filePath, 'utf8');
const uncommented = stripComments(content);
let match;
while ((match = CSS_IMPORT_RE.exec(uncommented)) !== null) {
// ...
}
```
2. If `CSS_IMPORT_RE` is reused across multiple files or helper functions in this script, ensure all of them pass the source through `stripComments` first. This will prevent commented-out imports (both `// import '...'` and imports inside `/* ... */`) from being counted in the drift check.
</issue_to_address>
### Comment 2
<location path="packages/editor/gulpfile.mjs" line_range="87" />
<code_context>
+ return OPTIONAL_PEERS.has(pkgName);
+}
+
+function createShadowStylesModule(value) {
+ const cssText = toTemplateLiteral(value);
+ const createStyleSheetBody = [
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the shadow-styles generation by using JSON.stringify-based string literals, a shared multi-line snippet for createStyleSheet, and extracting optional-peer/CSS logic into a helper module to keep the gulpfile focused on task wiring.
You can keep the current behavior but reduce complexity in a few focused spots:
### 1. Drop custom template-literal escaping
`toTemplateLiteral` can be removed by emitting a plain string literal via `JSON.stringify`, which handles all escaping for you and is easier to reason about.
```js
function createShadowStylesModule(value) {
const cssJson = JSON.stringify(value);
const createStyleSheetFn = `
function createStyleSheet() {
if (typeof CSSStyleSheet === 'undefined') {
throw new Error('Constructable stylesheets are not available in this environment.');
}
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(cssText);
return styleSheet;
}
`;
return {
esm: [
`export const cssText = ${cssJson};`,
createStyleSheetFn,
'export {createStyleSheet};',
'',
].join('\n'),
cjs: [
`const cssText = ${cssJson};`,
createStyleSheetFn,
'exports.cssText = cssText;',
'exports.createStyleSheet = createStyleSheet;',
'',
].join('\n'),
};
}
```
Then you can delete `toTemplateLiteral` entirely.
### 2. Make codegen more readable
Instead of building `createStyleSheet` as an array of lines and injecting it twice, use a single multi-line string (as above). This keeps the logic readable and easy to edit while still generating both ESM and CJS variants.
If you want to simplify further, you can factor the common snippet out:
```js
const CREATE_STYLESHEET_SNIPPET = `
function createStyleSheet() {
if (typeof CSSStyleSheet === 'undefined') {
throw new Error('Constructable stylesheets are not available in this environment.');
}
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(cssText);
return styleSheet;
}
`;
```
and reuse `CREATE_STYLESHEET_SNIPPET` in `createShadowStylesModule`.
### 3. Move optional-peer logic into a helper module
To keep the gulpfile focused on task wiring, push the peer-dependency parsing into a small helper. Behavior stays the same but the gulpfile reads more linearly.
`scripts/shadow-styles-helpers.js`:
```js
import {readFileSync} from 'node:fs';
import {createRequire} from 'node:module';
import pkg from '../package.json' with {type: 'json'};
const require = createRequire(import.meta.url);
const OPTIONAL_PEERS = new Set(
Object.entries(pkg.peerDependenciesMeta ?? {})
.filter(([, meta]) => meta?.optional)
.map(([name]) => name),
);
export const SHADOW_STYLE_IMPORTS = Object.freeze([
'@diplodoc/transform/dist/css/base.css',
'@diplodoc/transform/dist/css/_yfm-only.css',
'@diplodoc/cut-extension/runtime/styles.css',
'@diplodoc/file-extension/runtime/styles.css',
'@diplodoc/tabs-extension/runtime/styles.css',
'@diplodoc/quote-link-extension/runtime/styles.css',
'@diplodoc/folding-headings-extension/runtime/styles.css',
]);
export function readExternalShadowStyles() {
return SHADOW_STYLE_IMPORTS.map((cssImport) => {
try {
return readFileSync(require.resolve(cssImport), 'utf8');
} catch (err) {
if (err?.code === 'MODULE_NOT_FOUND' && isOptionalPeerImport(cssImport)) {
console.warn(
`[shadow-styles] Skipping optional peer CSS '${cssImport}' (package not installed).`,
);
return '';
}
throw err;
}
}).filter(Boolean).join('\n');
}
function isOptionalPeerImport(cssImport) {
const pkgName = cssImport.startsWith('@')
? cssImport.split('/', 2).join('/')
: cssImport.split('/', 1)[0];
return OPTIONAL_PEERS.has(pkgName);
}
```
`gulpfile` snippet:
```js
import {readExternalShadowStyles, SHADOW_STYLE_IMPORTS} from './scripts/shadow-styles-helpers.js';
// ... use readExternalShadowStyles() in the 'shadow-styles' task as you do now
```
This keeps all functionality but reduces the cognitive load in the gulpfile and makes the CSS/peer logic easier to test and evolve independently.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return OPTIONAL_PEERS.has(pkgName); | ||
| } | ||
|
|
||
| function createShadowStylesModule(value) { |
There was a problem hiding this comment.
issue (complexity): Consider simplifying the shadow-styles generation by using JSON.stringify-based string literals, a shared multi-line snippet for createStyleSheet, and extracting optional-peer/CSS logic into a helper module to keep the gulpfile focused on task wiring.
You can keep the current behavior but reduce complexity in a few focused spots:
1. Drop custom template-literal escaping
toTemplateLiteral can be removed by emitting a plain string literal via JSON.stringify, which handles all escaping for you and is easier to reason about.
function createShadowStylesModule(value) {
const cssJson = JSON.stringify(value);
const createStyleSheetFn = `
function createStyleSheet() {
if (typeof CSSStyleSheet === 'undefined') {
throw new Error('Constructable stylesheets are not available in this environment.');
}
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(cssText);
return styleSheet;
}
`;
return {
esm: [
`export const cssText = ${cssJson};`,
createStyleSheetFn,
'export {createStyleSheet};',
'',
].join('\n'),
cjs: [
`const cssText = ${cssJson};`,
createStyleSheetFn,
'exports.cssText = cssText;',
'exports.createStyleSheet = createStyleSheet;',
'',
].join('\n'),
};
}Then you can delete toTemplateLiteral entirely.
2. Make codegen more readable
Instead of building createStyleSheet as an array of lines and injecting it twice, use a single multi-line string (as above). This keeps the logic readable and easy to edit while still generating both ESM and CJS variants.
If you want to simplify further, you can factor the common snippet out:
const CREATE_STYLESHEET_SNIPPET = `
function createStyleSheet() {
if (typeof CSSStyleSheet === 'undefined') {
throw new Error('Constructable stylesheets are not available in this environment.');
}
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(cssText);
return styleSheet;
}
`;and reuse CREATE_STYLESHEET_SNIPPET in createShadowStylesModule.
3. Move optional-peer logic into a helper module
To keep the gulpfile focused on task wiring, push the peer-dependency parsing into a small helper. Behavior stays the same but the gulpfile reads more linearly.
scripts/shadow-styles-helpers.js:
import {readFileSync} from 'node:fs';
import {createRequire} from 'node:module';
import pkg from '../package.json' with {type: 'json'};
const require = createRequire(import.meta.url);
const OPTIONAL_PEERS = new Set(
Object.entries(pkg.peerDependenciesMeta ?? {})
.filter(([, meta]) => meta?.optional)
.map(([name]) => name),
);
export const SHADOW_STYLE_IMPORTS = Object.freeze([
'@diplodoc/transform/dist/css/base.css',
'@diplodoc/transform/dist/css/_yfm-only.css',
'@diplodoc/cut-extension/runtime/styles.css',
'@diplodoc/file-extension/runtime/styles.css',
'@diplodoc/tabs-extension/runtime/styles.css',
'@diplodoc/quote-link-extension/runtime/styles.css',
'@diplodoc/folding-headings-extension/runtime/styles.css',
]);
export function readExternalShadowStyles() {
return SHADOW_STYLE_IMPORTS.map((cssImport) => {
try {
return readFileSync(require.resolve(cssImport), 'utf8');
} catch (err) {
if (err?.code === 'MODULE_NOT_FOUND' && isOptionalPeerImport(cssImport)) {
console.warn(
`[shadow-styles] Skipping optional peer CSS '${cssImport}' (package not installed).`,
);
return '';
}
throw err;
}
}).filter(Boolean).join('\n');
}
function isOptionalPeerImport(cssImport) {
const pkgName = cssImport.startsWith('@')
? cssImport.split('/', 2).join('/')
: cssImport.split('/', 1)[0];
return OPTIONAL_PEERS.has(pkgName);
}gulpfile snippet:
import {readExternalShadowStyles, SHADOW_STYLE_IMPORTS} from './scripts/shadow-styles-helpers.js';
// ... use readExternalShadowStyles() in the 'shadow-styles' task as you do nowThis keeps all functionality but reduces the cognitive load in the gulpfile and makes the CSS/peer logic easier to test and evolve independently.
Decouple drift-check from gulpfile by extracting SHADOW_STYLE_IMPORTS
into scripts/shadow-styles-imports.mjs, so the CI check no longer
dynamically imports the gulpfile (which would also run
registerBuildTasks side effects) just to read one constant.
Broaden the drift-check regex to cover non-side-effect import forms
(`import x from 'pkg/x.css'`, `import * as x from`, named imports, and
dynamic `import('pkg/x.css')`), and strip block/line comments before
matching so commented-out imports do not produce false positives.
Split into two regexes (static vs dynamic) for readability.
Add an `assertNoCssImportRules` guard in the `shadow-styles` gulp task:
`CSSStyleSheet.replaceSync()` rejects `@import` rules at runtime, so
fail the build instead of letting Shadow DOM consumers crash. The guard
strips comments and string literals first to avoid false positives on
`@import` substrings inside CSS values.
Fold the standalone `Check Shadow Styles Imports` CI job into the
existing `tests` job — saves a redundant pnpm install (~30-60s) per PR.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Adds
@gravity-ui/markdown-editor/shadow-stylesfor Shadow DOM usage.cssTextgives you the bundled editor CSS as a stringcreateStyleSheet()returns a ready-to-useCSSStyleSheet@diplodoc/*CSS used by the editor@gravity-ui/uikitstylesAlso adds build/test/CI coverage to keep this export in sync.
Closes #1026